package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.IntentBizService;
import com.xgimi.open.skill.datamanager.biz.service.SkillVersionBizService;
import com.xgimi.open.skill.datamanager.biz.utils.BusinessIdUtil;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.domain.entity.IntentDO;
import com.xgimi.open.skill.datamanager.domain.entity.IntentSlotDO;
import com.xgimi.open.skill.datamanager.domain.entity.SkillVersionDO;
import com.xgimi.open.skill.datamanager.domain.entity.SlotDO;
import com.xgimi.open.skill.datamanager.domain.service.IntentService;
import com.xgimi.open.skill.datamanager.domain.service.IntentSlotService;
import com.xgimi.open.skill.datamanager.domain.service.SlotService;
import com.xgimi.open.skill.datamanager.service.request.InentReferenceSlotReqDTO;
import com.xgimi.open.skill.datamanager.service.request.IntentReqDTO;
import com.xgimi.open.skill.datamanager.service.response.IntentRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/8 14:32
 */
@Slf4j
@Service
public class IntentBizServiceImpl implements IntentBizService {
    private final IntentService intentService;
    private final SlotService slotService;
    private final RedisLockService redisLockService;
    private final SkillVersionBizService skillVersionBizService;
    private final IntentSlotService intentSlotService;


    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public IntentBizServiceImpl(IntentService intentService,
                                SlotService slotService,
                                RedisLockService redisLockService,
                                SkillVersionBizService skillVersionBizService,
                                IntentSlotService intentSlotService,
                                GlogRecorder glogRecorder) {
        this.intentService = intentService;
        this.slotService = slotService;
        this.redisLockService = redisLockService;
        this.skillVersionBizService = skillVersionBizService;
        this.intentSlotService = intentSlotService;
        this.glogRecorder = glogRecorder;
    }

    @Override
    public Boolean addInent(IntentReqDTO intentReqDTO) {
        IntentDO intentDO = new IntentDO();
        BeanUtils.copyProperties(intentReqDTO, intentDO);
        intentDO.setCreatedBy(intentReqDTO.getCreatedBy());
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentReqDTO.getSkillVid());
        // 技能写锁
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        boolean ret;
        try {
            // 1、查询技能版本表中 某个技能版本 是否已有重复的intent，根据intentName判断
            List<IntentDO> list = intentService.queryIntentByNameAndVid(intentDO);
            if (list.size() > 0) {
                throw new BizException(ExceptionErrorCode.INTENT_NAME_ALREADY_EXISTS);
            }
            // 2、新增product
            intentDO.setBusinessId(BusinessIdUtil.getIntentUUID());
            ret = intentService.saveIntent(intentDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        // glog日志接入
        glogRecorder.recordAdd(intentReqDTO.getCreatedBy(),
                "intent", "addInent",
                String.valueOf(ret), intentReqDTO);
        return ret;
    }


    @Override
    public List<IntentRespDTO> list(IntentReqDTO intentReqDTO) {
        Long skillVid = intentReqDTO.getSkillVid();
        skillVersionBizService.checkSkillVersion(skillVid);
        IntentDO intentDO = new IntentDO();
        BeanUtils.copyProperties(intentReqDTO, intentDO);
        List<IntentRespDTO> list = listBySkillVid(skillVid);
        return list;
    }

    @Override
    public List<IntentRespDTO> listBySkillVid(Long skillVid) {
        List<IntentDO> intentDOs = intentService.listBySkillVid(skillVid);
        // 若有结果，则将 DO 转成 DTO
        List<IntentRespDTO> list = intentDOs.stream().map(d -> {
            IntentRespDTO dto = new IntentRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return list;
    }

    @Override
    public Boolean deleteIntent(IntentReqDTO intentReqDTO) {
        BizAssert.notNull(intentReqDTO.getId(), ExceptionErrorCode.Intent_ID_IS_NULL);
        // 先根据技能id查询 是否存在该意图
        IntentDO res = intentService.queryById(intentReqDTO.getId());
        BizAssert.notNull(res, ExceptionErrorCode.INTENT_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(res.getSkillVid());
        boolean ret;
        // 技能写锁
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            IntentDO intentDO = new IntentDO();
            BeanUtils.copyProperties(intentReqDTO, intentDO);
            // 先删除关联该意图的槽位信息
            intentSlotService.deleteByIntentId(intentDO.getId());
            // 删除技能
            ret = intentService.removeById(intentDO.getId());
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        // glog日志接入
        glogRecorder.recordDel(intentReqDTO.getUpdatedBy(),
                "intent", "delete",
                intentReqDTO.toString());
        return ret;
    }


    @Override
    public Boolean updateIntent(IntentReqDTO intentReqDTO) {
        BizAssert.notNull(intentReqDTO.getId(), ExceptionErrorCode.Intent_ID_IS_NULL);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentReqDTO.getSkillVid());
        // 先根据技能id查询 是否存在该技能
        IntentDO from = intentService.queryById(intentReqDTO.getId());
        BizAssert.notNull(from, ExceptionErrorCode.INTENT_NOT_EXISTS);
        boolean ret;
        // 技能写锁
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            IntentDO intentDO = new IntentDO();
            BeanUtils.copyProperties(intentReqDTO, intentDO);
            intentDO.setCreatedBy(intentReqDTO.getUpdatedBy());
            if (!StringUtils.isEmpty(intentDO.getIntentName())) {
                // 判断当前 技能版本下 是否存在 相同名字的其他意图，则抛出异常
                List<IntentDO> list = intentService.queryByNameByVidAndNoId(intentDO);
                if (list.size() > 0) {
                    throw new BizException(ExceptionErrorCode.INTENT_NAME_ALREADY_EXISTS);
                }
            }
            // 修改意图
            ret = intentService.updateById(intentDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }

        // 添加glog日志
        IntentDO to = intentService.queryById(intentReqDTO.getId());
        glogRecorder.recordUpdate(intentReqDTO.getUpdatedBy(),
                "intent", "update",
                String.valueOf(ret), from, to);
        return ret;
    }

    @Transactional
    @Override
    public Boolean referenceSlot(InentReferenceSlotReqDTO inentReferenceSlotReqDTO) {
        Long intentId = inentReferenceSlotReqDTO.getIntentId();
        Long slotId = inentReferenceSlotReqDTO.getSlotId();
        String userName = inentReferenceSlotReqDTO.getUpdatedBy();
        // 先根据技能id查询 是否存在该技能
        IntentDO intentDO = intentService.queryById(intentId);
        BizAssert.notNull(intentDO, ExceptionErrorCode.INTENT_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentDO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            SlotDO slotDO = slotService.queryById(slotId);
            BizAssert.notNull(slotDO, ExceptionErrorCode.SOLT_NOT_EXISTS);
            // 先查询是否已存在引用关系
            IntentSlotDO intentSlotDO = intentSlotService.queryByIntentIdAndSlotId(intentId, slotId);
            if (Objects.isNull(intentSlotDO)) {
                // 将 intentId - slotId 写入到【intent_slot】表中
                IntentSlotDO intentSlot = new IntentSlotDO();
                intentSlot.setIntentId(intentId);
                intentSlot.setSlotId(slotId);
                intentSlot.setCreatedBy(userName);
                intentSlotService.save(intentSlot);
            }
            ret = Boolean.TRUE;
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        return ret;
    }


    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }
}
